`include "defines.v"
`include "inst_defines.v"
`include "aluop_defines.v"

module exe(
    input wire clk,
    input wire rst,

    input  wire    [`RAM_BUS]         pc_i,
    input  wire    [`REG_WIDTH]       op1_i,
    input  wire    [`REG_WIDTH]       op2_i,
    input  wire    [`REG_WIDTH]       imm_i,
    input  wire    [5 : 0]            aluop_i,
    input  wire                       skip_id_i,
    input  wire                       id_reg_valid_i,


    input  wire                       rd_w_ena_i,
    input  wire    [`REG_BUS]         rd_w_addr_i,
  
    input  wire    [`REG_WIDTH]       csr_data_i, 

    output reg                        rd_w_ena_o,
    output reg     [`REG_BUS]         rd_w_addr_o, 
    output reg     [`REG_WIDTH]       rd_data_o,
 
    output reg     [`REG_WIDTH]       csr_data_o,    //csr寄存器写操作放在exe执行

    //访存相关
    output reg     [5 : 0]            aluop_o,
    output reg     [`RAM_BUS]         mem_addr_o,
    output reg     [`REG_WIDTH]       rs2_o,         //store将第二个操作数存到D-RAM


    output reg                        halt_ena_o,
    output wire                       skip_exe_o,
    output reg                        exe_data_valid_o                  


);
    //skip
    reg    skip_exe;
    wire   clint_skip;
    assign clint_skip = (mem_addr_o == `MTIMECMP_ADDR | mem_addr_o == `MTIME_ADDR);
    assign skip_exe_o = skip_exe | skip_id_i | clint_skip;

    wire putch_tag = aluop_i == `ALUOP_PUTCH;

    // 部分计算结果
    wire  [`REG_WIDTH]     add_result;
    wire  [`REG_WIDTH]     sub_result;
    wire  [`REG_WIDTH]     sll_result;
    wire  [`REG_WIDTH]     srl_result;
    wire  [`REG_WIDTH]     sra_result;
    wire  [31:0]           sllw_result;
    wire  [31:0]           srlw_result;
    wire  [31:0]           sraw_result;

    wire  [`REG_WIDTH]     sra_l;            //sra计算时的左边符号部分
    wire  [`REG_WIDTH]     sra_r;            //sra计算时的右边移位部分
    wire  [31:0]           sraw_l;           //sraw计算时的左边符号部分
    wire  [31:0]           sraw_r;           //sraw计算时的右边移位部分

    //提前计算出一些下面会用到的结果
    assign add_result  = op1_i + op2_i;
    assign sub_result  = op1_i - op2_i;
    assign sll_result  = op1_i << op2_i[5:0]; //sll,slli
    assign srl_result  = op1_i >> op2_i[5:0]; //srl,srli
    assign sllw_result = op1_i[31:0] << op2_i[4:0]; //sllw,slliw
    assign srlw_result = op1_i[31:0] >> op2_i[4:0]; //srlw,srliw
    //算数右移计算
    assign sra_l       = 64'hffff_ffff_ffff_ffff >> op2_i[5:0];
    assign sra_r       = op1_i >> op2_i[5:0];
    assign sra_result  = (sra_l & sra_r) | ({64{op1_i[63]}} & (~sra_l));

    assign sraw_l       = 32'hffff_ffff >> op2_i[4:0];
    assign sraw_r       = op1_i[31:0] >> op2_i[4:0];
    assign sraw_result  = (sraw_l & sraw_r) | ({32{op1_i[31]}} & (~sraw_l));

    //alu
    always @ (*) begin
        rd_w_ena_o    = rd_w_ena_i;
        rd_w_addr_o   = rd_w_addr_i;
        rs2_o         = op2_i;
        aluop_o       = aluop_i;
        if(rst == `RST) begin
            rd_data_o  = `ZERO_WORD;
            exe_data_valid_o = 0;
        end
        else begin
            case (aluop_i)
                `ALUOP_NOP : begin exe_data_valid_o = 1'b1; rd_data_o = `ZERO_WORD; end
                `ALUOP_ADD : begin exe_data_valid_o = 1'b1; rd_data_o = add_result; end
                `ALUOP_SUB : begin exe_data_valid_o = 1'b1; rd_data_o = sub_result; end
                `ALUOP_ADDW: begin exe_data_valid_o = 1'b1; rd_data_o = {{32{add_result[31]}}, add_result[31:0] }; end
                `ALUOP_SUBW: begin exe_data_valid_o = 1'b1; rd_data_o = {{32{sub_result[31]}}, sub_result[31:0] }; end        
                `ALUOP_SLT : begin exe_data_valid_o = 1'b1; rd_data_o = $signed(op1_i) < $signed(op2_i); end
                `ALUOP_SLTU: begin exe_data_valid_o = 1'b1; rd_data_o = op1_i < op2_i; end
                `ALUOP_AND : begin exe_data_valid_o = 1'b1; rd_data_o = op1_i & op2_i; end
                `ALUOP_OR  : begin exe_data_valid_o = 1'b1; rd_data_o = op1_i | op2_i; end
                `ALUOP_XOR : begin exe_data_valid_o = 1'b1; rd_data_o = op1_i ^ op2_i;end
                `ALUOP_SLL : begin exe_data_valid_o = 1'b1; rd_data_o = sll_result; end
                `ALUOP_SRL : begin exe_data_valid_o = 1'b1; rd_data_o = srl_result; end
                `ALUOP_SRA : begin exe_data_valid_o = 1'b1; rd_data_o = sra_result; end
                `ALUOP_SLLW: begin exe_data_valid_o = 1'b1; rd_data_o = {{32{sllw_result[31]}}, sllw_result[31:0] }; end    
                `ALUOP_SRLW: begin exe_data_valid_o = 1'b1; rd_data_o = {{32{srlw_result[31]}}, srlw_result[31:0] }; end                 
                `ALUOP_SRAW: begin exe_data_valid_o = 1'b1; rd_data_o = {{32{sraw_result[31]}}, sraw_result[31:0] }; end

                `ALUOP_LUI : begin exe_data_valid_o = 1'b1; rd_data_o = imm_i; end
                `ALUOP_AUIPC:begin exe_data_valid_o = 1'b1; rd_data_o = pc_i + imm_i; end

                `ALUOP_JAL : begin exe_data_valid_o = 1'b1; rd_data_o = pc_i + 4; end
                `ALUOP_JALR: begin exe_data_valid_o = 1'b1; rd_data_o = pc_i + 4; end

                `ALUOP_CSRRW:begin exe_data_valid_o = 1'b1; rd_data_o = csr_data_i; end
                `ALUOP_CSRRS:begin exe_data_valid_o = 1'b1; rd_data_o = csr_data_i; end
                `ALUOP_CSRRC:begin exe_data_valid_o = 1'b1; rd_data_o = csr_data_i; end

                default    : begin exe_data_valid_o = 1'b0; rd_data_o = `ZERO_WORD; end                                     
            endcase
        end
    end

    always @ (*) begin
        if(rst == `RST) begin
            mem_addr_o = `ZERO_WORD; 
        end
        else begin
            case (aluop_i)
                `ALUOP_LB  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LH  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LW  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LD  : mem_addr_o = op1_i + imm_i;
                `ALUOP_LBU : mem_addr_o = op1_i + imm_i;
                `ALUOP_LHU : mem_addr_o = op1_i + imm_i;
                `ALUOP_LWU : mem_addr_o = op1_i + imm_i;

                `ALUOP_SB  : mem_addr_o = op1_i + imm_i;
                `ALUOP_SH  : mem_addr_o = op1_i + imm_i;
                `ALUOP_SW  : mem_addr_o = op1_i + imm_i; 
                `ALUOP_SD  : mem_addr_o = op1_i + imm_i;

                default    : mem_addr_o = `ZERO_WORD;
            endcase
        end
    end
    
    always @ (*) begin
        if(rst == `RST) 
            begin
                halt_ena_o = 1'b0; 
                skip_exe = 1'b0;                
            end
        else begin
            case(aluop_i)
                `ALUOP_FALT  : begin 
                                    //$display("The program falt!"); 
                                    //$display("The a0 is %h. ", op1_i);
                                    halt_ena_o = 1'b1; 
                                    skip_exe = 1'b1;
                                end
                `ALUOP_PUTCH : begin
                                    halt_ena_o = 1'b0; 
                                    skip_exe = 1'b1;
                                    //$write("%c",op1_i);
                                end 
                                
                default :
                    begin
                        halt_ena_o = 1'b0; 
                        skip_exe = 1'b0;                
                    end
            endcase
        end
    end

    always @ (posedge clk) begin
        if(id_reg_valid_i)
            case(aluop_i)
                `ALUOP_FALT  : begin 
                                    $display("The program falt!"); 
                                    $display("The a0 is %h. ", op1_i);
                                end
                `ALUOP_PUTCH : begin
                                    $write("%c",op1_i);
                                end                 
                default :;
            endcase
    end
// csr
    always @ (*) begin
        if(rst == `RST) 
            begin
                csr_data_o  = `ZERO_WORD;
            end
        else
            case(aluop_i)
                `ALUOP_CSRRW : csr_data_o = op1_i;
                `ALUOP_CSRRS : csr_data_o = op1_i | csr_data_i;
                `ALUOP_CSRRC : csr_data_o = ~op1_i & csr_data_i;
                default      : csr_data_o  = `ZERO_WORD;
            endcase
    end


endmodule